import type { DateLib } from "../classes/DateLib.js";
import { CalendarDay } from "../classes/index.js";
import type {
  DayPickerProps,
  MoveFocusBy,
  MoveFocusDir,
} from "../types/index.js";
import { dateMatchModifiers } from "../utils/dateMatchModifiers.js";

import { getFocusableDate } from "./getFocusableDate.js";

/**
 * Determines the next focusable day in the calendar.
 *
 * This function recursively calculates the next focusable day based on the
 * movement direction and modifiers applied to the days.
 *
 * @param moveBy The unit of movement (e.g., "day", "week").
 * @param moveDir The direction of movement ("before" or "after").
 * @param refDay The currently focused day.
 * @param calendarStartMonth The earliest month the user can navigate to.
 * @param calendarEndMonth The latest month the user can navigate to.
 * @param props The DayPicker props, including modifiers and configuration
 *   options.
 * @param dateLib The date library to use for date manipulation.
 * @param attempt The current recursion attempt (used to limit recursion depth).
 * @returns The next focusable day, or `undefined` if no focusable day is found.
 */
export function getNextFocus(
  moveBy: MoveFocusBy,
  moveDir: MoveFocusDir,
  refDay: CalendarDay,
  calendarStartMonth: Date | undefined,
  calendarEndMonth: Date | undefined,
  props: Pick<
    DayPickerProps,
    "disabled" | "hidden" | "modifiers" | "ISOWeek" | "timeZone"
  >,
  dateLib: DateLib,
  attempt: number = 0,
): CalendarDay | undefined {
  if (attempt > 365) {
    // Limit the recursion to 365 attempts
    return undefined;
  }

  const focusableDate = getFocusableDate(
    moveBy,
    moveDir,
    refDay.date,
    calendarStartMonth,
    calendarEndMonth,
    props,
    dateLib,
  );

  const isDisabled = Boolean(
    props.disabled &&
      dateMatchModifiers(focusableDate, props.disabled, dateLib),
  );

  const isHidden = Boolean(
    props.hidden && dateMatchModifiers(focusableDate, props.hidden, dateLib),
  );

  const targetMonth = focusableDate;
  const focusDay = new CalendarDay(focusableDate, targetMonth, dateLib);

  if (!isDisabled && !isHidden) {
    return focusDay;
  }

  // Recursively attempt to find the next focusable date
  return getNextFocus(
    moveBy,
    moveDir,
    focusDay,
    calendarStartMonth,
    calendarEndMonth,
    props,
    dateLib,
    attempt + 1,
  );
}
